var WebSocketServer = require('websocket').server;
var http = require('http');

var AppUtils = require('../utils/AppUtils');
var IPAddress = require('./IPAddress');
var WSMsg = require('./WSMsg');
var WSClient = require('./WSClient');

/**
 * WebSocket 服务：负责添加节点列表，而在WSClient中负责添加客户端连接
 */
class WSServer {

  constructor(ip, port) {
    this.ipAddress = new IPAddress(ip, port);
    this.appUtils = AppUtils.getSingle();

    //用户自定义 客户端消息处理函数
    this.userServerOnMsgFunc = null;
    //用户自定义 服务端消息处理函数
    this.userClientOnMsgFunc = null;

    //1.1 创建http服务
    this.httpServer = http.createServer(function (request, response) {
      console.log((new Date()) + ' Received http request for ' + request.url);
      response.writeHead(404);
      response.end();
    });
  }

  /**
   * 0.1 设置接收消息事件方法 - 提供给p2p模块使用者
   * @param funcServer(msg, connObj, wsServerObj)
   * @param funcClient(msg, connObj, wsServerObj)
   */
  setOnMsgEvent(funcServer,funcClient) { 
    if (funcServer && funcServer instanceof Function) {
      this.userServerOnMsgFunc = funcServer;
    } else { 
      throw new Error('funcServer 必须是一个函数');
    }

    if (funcClient && funcClient instanceof Function) {
      this.userClientOnMsgFunc = funcClient;
    } else { 
      throw new Error('funcClient 必须是一个函数');
    }
  }

  /**
   * 1.0 启动本机 WebSocket 服务端
   */
  startWSServer() {
    // 1.1 开启websocket服务端http监听
    this.httpServer.listen(this.ipAddress.port, function () {
      var appUtils = AppUtils.getSingle();
      
      appUtils.logger.logSTitle('startWSServer', 'express服务【' + appUtils.webIpAddress.getURI() + '】启动 WebSocekt服务，端口：' + WSServer.getSingle().ipAddress.port);
    });

    // 1.2 创建 wss服务端 对象，依赖 http对象 进行通信（相当于使用Http服务里的socket）
    this.wssServer = new WebSocketServer({
      httpServer: this.httpServer,
      autoAcceptConnections: false
    });

    // 1.3 将 ws服务端 uri 存入 在线节点列表
    AppUtils.getSingle().onlineNodeList.add(this.ipAddress);

    this.appUtils.logger.logSContent('startWSServer', 'onlineNodeList=', AppUtils.getSingle().onlineNodeList);

    // 1.4 注册ws监听事件
    this.regWSEvents();

    //* 链式编程之趣
    return this;
  }

  /**
   * 2.0 注册 WebSocket 服务端 各个监听事件
   */
  regWSEvents() {
    //2.2 连接事件
    this.wssServer.on('request', function (request) {
      //2.2.1 创建与请求连接的 客户端 连接通道 对象
      var connection = request.accept('echo-protocol', request.origin);

      //2.2.2 调用连接请求事件
      WSServer.getSingle().onClientRequest(connection);

      //2.2.3 监听客户端消息事件
      connection.on('message', (message) => {
        if (message.type === 'utf8') {
          WSServer.getSingle().onMessage(message.utf8Data, connection);
        }
      });

      //2.2.4 监听客户端连接关闭事件(关闭原因代码，关闭原因描述)
      connection.on('close', (reasonCode, description) => {
        WSServer.getSingle().onConnectionClose(reasonCode, description, connection);
      });
    });
  }


  /**
   * 2.1 客户端连接请求事件
   * @param connObj 与客户端的连接通道
   */
  onClientRequest(connObj) {
    this.appUtils.logger.logSTitle('onClientRequest', 'WebSocket服务接收到来自[' + connObj.socket.remoteAddress + ',socket port:' + connObj.socket.remotePort + ']的连接请求，并建立C2MS连接成功！');
  }

  /**
   * 2.2 消息监听事件
   * @param msg 收到的UTF8消息字符串
   * @param connObj 与客户端的连接通道
   */
  onMessage(msgUtf8, connObj) {
    this.appUtils.logger.logSTitle('onMessage', '处理 [' + connObj.remoteAddress + ']客户端 消息请求，接收到消息：', msgUtf8);
    
    if (WSMsg.isWSMsg(msgUtf8)) {
      var wsMsg = WSMsg.json2WSMsgObj(msgUtf8);
      if (wsMsg.state === WSMsg.StateType.RequestOnlineNodeList) {
        this.sendNodeList(wsMsg, connObj);
      } else if (wsMsg.state === WSMsg.StateType.Connect) {
        this.connectServerBack(wsMsg);
      }
    } else {
      //调用用户自定义的 消息处理方法
      this.userServerOnMsgFunc(msgUtf8, connObj, this);
      
    }

    // if (wsMsg.state === WSMsg.StateType.SynchronousBlockData) {
    // }
  }


  /**
   * 2.3 与客户端连接关闭事件
   */
  onConnectionClose(reasonCode, description,connObj) { 
    AppUtils.getSingle().logger.logSContent('wsConnectionOnClose', '服务端[' + WSServer.getSingle().ipAddress.getURI() + ']与客户端[' + connObj.socket.remoteAddress + '，socket port:' + connObj.socket.remotePort + ']连接关闭~ code:' + reasonCode + ', 原因：' + description);
  }

  /**
   * 3.1 处理 客户端请求 在网列表，并与之建立双向连接
   * @param wsMsgObj 消息对象
   * @param connObj 于客户端的连接通道
   */
  sendNodeList(wsMsgObj, connObj) {
    // 1. 将 json 数据 转成 IPAddress 对象
    var clientIPAddress = IPAddress.json2Obj(wsMsgObj.content);

    this.appUtils.logger.logSContent('processOnlineListRequest', '客户端[' + clientIPAddress.getURI() + ']请求 在网节点列表 ~！');
      
    // 2.将客户端的ServerURI 存入 在网节点列表，但连接由Client的事件里添加到集合
    this.appUtils.onlineNodeList.add(clientIPAddress);

    // 3. 发送 在网节点 列表 给 客户端
    wsMsgObj.state = WSMsg.StateType.ResponseOnlineNodeList;
    wsMsgObj.content = this.appUtils.onlineNodeList.toIpAddressJsonArray();
    
    this.appUtils.logger.logSContent('processOnlineListRequest', '发回最新在网节点列表：', wsMsgObj);
    
    connObj.sendUTF(wsMsgObj.toJson());

    // 4.判断是否已和该客户端建立连接，如果没有，则先要向对方发起客户端连接
    this.appUtils.logger.logSContent('processOnlineListRequest', '开始向单向连接客户端[' + clientIPAddress.getURI() + ']发送请求~！');

    // 5. 创建本节点客户端，与客户机服务端发送连接请求，尝试建立双向连接
    this.connectServer(clientIPAddress);
  }

  /**
   * 3.2 创建本节点客户端，与客户机服务端发送连接请求，尝试建立双向连接
   * @param {*} wsMsgObj 
   */
  connectServerBack(wsMsgObj) {
    this.appUtils.logger.logSTitle('connectBack', '客户端连接本节点', this.appUtils.clientConnectionList);

    // 1. 将 json 数据 转成 IPAddress 对象
    var clientIPAddress = IPAddress.json2Obj(wsMsgObj.content);

    this.appUtils.onlineNodeList.add(clientIPAddress);

    this.connectServer(clientIPAddress);
  }

  /**
   * 3.3 创建本节点客户端，与客户机服务端发送连接请求，尝试建立双向连接
   * @param targetIpAddr 客户机服务端IP
   */
  connectServer(targetIpAddr) { 
    // 4.1 创建本节点客户端，与客户机服务端发送连接请求，尝试建立双向连接
    var client = new WSClient(targetIpAddr.ip, targetIpAddr.port, WSClient.ConnState.DoubleConnected);
    client.setOnMsgEvent(this.userClientOnMsgFunc);
    client.connect();

    this.appUtils.logger.logSContent('connectClientServerBack', '发送双向连接请求完毕~~~');

  }

  /**
   * 4.0 广播区块链数据给其它节点
   */
  broadBlockData(data) {
    if (typeof data !== 'string')
      data = JSON.stringify(data);
    
    this.appUtils.logger.logSContent('broadBlockData(data)', '广播了一个最新的区块：', data);
    
    this.wssServer.broadcastUTF(data);
  }
  // -------------------------------------------------

  /**
   * 0.0 获取 WebSocket服务 单例对象
   */
  static getSingle(wsIp, wsPort) {
    if (!this.onlyWSServer) {
      // 如果直接传的是 ipAddress
      if (wsIp instanceof IPAddress) {
        this.onlyWSServer = new WSServer(wsIp.ip, wsIp.port);
      } else {
        this.onlyWSServer = new WSServer(wsIp,wsPort);
      }
    }
    return this.onlyWSServer;
  }
}

WSServer.onlyWSServer = null;

module.exports = WSServer;